import datetime
from typing import TYPE_CHECKING

import sqlalchemy
from sqlalchemy.dialects import postgresql
from sqlalchemy.orm import Mapped

from h.db import Base, mixins
from h.models import helpers

if TYPE_CHECKING:
    from h.models.user import User


class Token(Base, mixins.Timestamps):
    r"""
    An API access token for a user.

    These fall into two categories:

    - Long-lived developer tokens, which are generated for an account for
      third-party integrations. These do not expire.
    - Temporary access tokens, which are currently only issued from JWTs
      generated by third party `AuthClient`\\ s. These do expire.
    """

    __tablename__ = "token"

    id = sqlalchemy.Column(sqlalchemy.Integer, autoincrement=True, primary_key=True)

    value = sqlalchemy.Column(sqlalchemy.UnicodeText(), nullable=False, unique=True)

    #: A timestamp after which this token will no longer be considered valid.
    #: A NULL value in this column indicates a token that does not expire.
    expires = sqlalchemy.Column(sqlalchemy.DateTime, nullable=True)

    #: A refresh token that can be exchanged for a new token (with a new value
    #: and expiry time). A NULL value in this column indicates a token that
    #: cannot be refreshed.
    refresh_token = sqlalchemy.Column(
        sqlalchemy.UnicodeText(), unique=True, nullable=True
    )

    #: A timestamp after which this token's refresh token will no longer be
    #: considered valid. A NULL value in this column indicates a refresh token
    #: that does not expire.
    refresh_token_expires = sqlalchemy.Column(sqlalchemy.DateTime, nullable=True)

    _authclient_id = sqlalchemy.Column(
        "authclient_id",
        postgresql.UUID(as_uuid=False),
        sqlalchemy.ForeignKey("authclient.id", ondelete="cascade"),
        nullable=True,
    )

    user_id = sqlalchemy.Column(
        sqlalchemy.Integer,
        sqlalchemy.ForeignKey("user.id", ondelete="cascade"),
        index=True,
        nullable=False,
    )
    user: Mapped["User"] = sqlalchemy.orm.relationship(back_populates="tokens")

    #: The authclient which created the token.
    #: A NULL value means it is a developer token.
    authclient = sqlalchemy.orm.relationship("AuthClient")

    @property
    def expired(self):
        """Return True if this access token has expired, False otherwise."""
        if self.expires:
            return datetime.datetime.utcnow() > self.expires  # noqa: DTZ003

        return False

    @property
    def refresh_token_expired(self):
        """Return True if this refresh token has expired, False otherwise."""
        if self.refresh_token_expires:
            return datetime.datetime.utcnow() > self.refresh_token_expires  # noqa: DTZ003

        return False

    @property
    def ttl(self):
        """Get the amount of time from now until this token expires, in seconds."""
        if not self.expires:
            return None

        now = datetime.datetime.utcnow()  # noqa: DTZ003
        ttl = self.expires - now
        ttl_in_seconds = ttl.total_seconds()
        # We truncate (rather than round) ttl_in_seconds to get an int.
        # For example 2.3 beccomes 2, but 2.9 also becomes 2.
        ttl_in_seconds_truncated = int(ttl_in_seconds)
        return ttl_in_seconds_truncated

    def __repr__(self):
        return helpers.repr_(self, ["id", "user_id", "_authclient_id"])
